import {
  CommandDialog,
  Icon,
  ParameterObjectType,
  Types,
  pickedFaceData,
  pickedLineData,
} from '@amcax/base';
import { InputWithFormat } from '@app-base/common/components/command-form/InputWithFormat';
import { CommandFormWrapper } from '@app-cad/common/components/command-form';
import { PickInputForFocus } from '@app-cad/common/components/command-form/auto-focus/pick-input';
import { CommandFormFooter } from '@app-cad/common/components/command-form/footer';
import { PickInputValueType } from '@app-cad/common/components/pick-input/3d';
import { useCommand } from '@app-cad/common/hooks/useCommand';
import { useTools } from '@app-cad/common/hooks/useTools';
import { getIdOfPickedObj } from '@app-cad/common/utils/render';
import { ctx2, docMgr, previewer } from '@app-cad/module';
import { wsApi } from '@app-cad/server';
import { FeaturedCmdDialogParamsWithId } from '@app-cad/types';
import { useAsyncEffect, useMemoizedFn } from 'ahooks';
import { Checkbox, Select } from 'antd';
import { useEffect, useMemo, useState } from 'react';
import { eventCenterManager } from '@app-base/common/events/EventCenterManager';

type Params = {
  mode: LimitMode;
  profile: Array<string>;
  vector?: string;
  reverse?: boolean;
  distance?: number;
  plane?: string;
  surface?: string;
  surfPoint?: [number, number, number];
  useDir1: boolean;
  mode2: LimitMode;
  reverse2?: boolean;
  distance2?: number;
  plane2?: string;
  surface2?: string;
  surfPoint2?: [number, number, number];
  useDir2: boolean;
  mirror?: boolean;
  reverseSideCut?: boolean;
};

type LimitMode = 'distance' | 'next' | 'last' | 'plane' | 'surface';
type SingleLimit = {
  mode: LimitMode;
  reverse?: boolean;
  plane?: pickedFaceData;
  surface?: pickedFaceData;
  surfPoint?: [number, number, number];
  distance?: number;
};

export function BaseExtrudeCommand({
  title,
  onClose,
  props,
  createFunction,
  cmdId,
}: FeaturedCmdDialogParamsWithId & {
  createFunction: (params: any, isPreview: boolean) => Promise<void>;
}) {
  const { picker } = useTools();

  const [pickedProfile, setPickedProfile] = useState<
    (pickedLineData | pickedFaceData)[]
  >([]);

  // 仅考虑第一个
  const isProfileSketch =
    docMgr.getDocumentObjectById(pickedProfile[0]?.idInfo?.o)?.type ===
    Types.Sketcher;

  const [pickedVector, setPickedVector] = useState<
    pickedLineData | pickedFaceData
  >();

  const [firstLimit, setFirstLimit] = useState<SingleLimit>({
    mode: 'distance',
    distance: 20,
  });

  const [secondLimit, setSecondLimit] = useState<SingleLimit>({
    mode: 'distance',
    distance: 0,
  });

  const [mirrorLimit, setMirrorLimit] = useState<boolean>(false);
  const [reverseSideCut, setReverseSideCut] = useState<boolean>(false);

  const params: Params = useMemo(() => {
    const isLimitValid = (limit: SingleLimit) =>
      Boolean(limit.distance || limit.plane || limit.surface) ||
      ['next', 'last'].includes(limit.mode);
    return {
      profile: pickedProfile.map(getIdOfPickedObj),
      vector: getIdOfPickedObj(pickedVector),
      mode: firstLimit.mode,
      reverse: firstLimit.reverse,
      distance: firstLimit.distance,
      plane: firstLimit.plane?.meshId,
      surface: firstLimit.surface?.meshId,
      surfPoint: firstLimit.surfPoint,
      useDir1: isLimitValid(firstLimit),
      mode2: secondLimit.mode,
      reverse2: secondLimit.reverse,
      distance2: secondLimit.distance,
      plane2: secondLimit.plane?.meshId,
      surface2: secondLimit.surface?.meshId,
      surfPoint2: secondLimit.surfPoint,
      useDir2: isLimitValid(secondLimit),
      mirror: mirrorLimit,
      reverseSideCut: reverseSideCut,
    } as Params;
  }, [
    pickedProfile,
    pickedVector,
    firstLimit,
    secondLimit,
    mirrorLimit,
    reverseSideCut,
  ]);

  useEffect(() => {
    if (firstLimit.mode === 'distance' && mirrorLimit === true) {
      setSecondLimit({
        mode: firstLimit.mode,
        distance: firstLimit.distance,
        reverse: firstLimit.reverse,
      });
    } else {
      setMirrorLimit(false);
    }
  }, [firstLimit.distance, firstLimit.reverse, mirrorLimit]);

  const validate = () => {
    const validateSingleLimit = (limit: SingleLimit) => {
      if (['next', 'last', 'distance'].includes(limit.mode)) {
        return true;
      }

      return (
        (limit.mode === 'plane' && limit.plane != null) ||
        (limit.mode === 'surface' && limit.surface != null)
      );
    };

    return (
      pickedProfile.length > 0 &&
      (isProfileSketch || pickedVector != null) &&
      validateSingleLimit(firstLimit) &&
      (validateSingleLimit(secondLimit) || !params.useDir2)
    );
  };

  const finishValid = useMemo(validate, [params]);

  const execute = useMemoizedFn(async (isPreview: boolean) => {
    previewer.clear();

    if (validate()) {
      try {
        const result = await (props == null
          ? createFunction(params, isPreview)
          : wsApi.setParams(props.shape, params, isPreview));

        if (isPreview) {
          previewInCommand(result);
        }
      } catch (e) {
        // do nothing
        console.error(e);
      }
    }
  });

  useAsyncEffect(async () => {
    if (!finishValid) {
      return;
    }

    await execute(true);
  }, [params, cmdId]);

  useAsyncEffect(async () => {
    if (props == null) {
      return;
    }

    const {
      profile,
      vector,
      mode,
      distance,
      reverse,
      plane,
      surface,
      surfPoint,
      mode2,
      distance2,
      reverse2,
      plane2,
      surface2,
      surfPoint2,
      mirror,
      reverseSideCut,
    } = props.params as Params;

    await initEditDependency([
      ...profile,
      vector,
      plane,
      surface,
      plane2,
      surface2,
    ]);

    const getLimit = (
      mode: LimitMode,
      distance: number,
      reverse: boolean,
      plane: string,
      surface: string,
      surfPoint: [number, number, number],
    ) => {
      return {
        mode,
        distance,
        reverse,
        plane:
          plane && (picker.generatePickObjByPickID(plane) as pickedFaceData),
        surface:
          surface &&
          (picker.generatePickObjByPickID(surface) as pickedFaceData),
        surfPoint,
      } as SingleLimit;
    };

    setPickedProfile(
      (profile ?? []).map(
        (dxid) => picker.generatePickObjByPickID(dxid) as pickedLineData,
      ),
    );

    setPickedVector(
      picker.generatePickObjByPickID(vector) as pickedFaceData | pickedLineData,
    );

    setFirstLimit(getLimit(mode, distance, reverse, plane, surface, surfPoint));
    setSecondLimit(
      getLimit(mode2, distance2, reverse2, plane2, surface2, surfPoint2),
    );

    setMirrorLimit(mirror);
    setReverseSideCut(!!reverseSideCut);
  }, []);

  const finishAction = async () => {
    await execute(false);
    cancelAction();
  };

  const cancelAction = () => {
    previewer.clear();
    onClose();
  };

  const { initEditDependency, initingEdit, previewInCommand } = useCommand(
    'cmd-extrude-cut',
    {
      editDxid: props?.dxid,
      dependencies: (props as any)?.prelinks || [],
      onClose: cancelAction,
      finishDraw: async () => {
        await new Promise((resolve) => setTimeout(resolve));
        await finishAction();
      },
    },
  );

  const renderLimit = (
    onChange: (newVal: SingleLimit) => void,
    val: SingleLimit,
    title: string,
    lockWithMirrorLimit: boolean,
  ) => {
    const isDisabled = lockWithMirrorLimit && mirrorLimit;
    return (
      <>
        <div className='text-sm'>{title}</div>
        <div className='flex'>
          <Select
            disabled={isDisabled}
            className='flex-1'
            value={val.mode}
            options={[
              {
                label: '尺寸',
                value: 'distance',
              },
              {
                label: '直到下一个',
                value: 'next',
              },
              {
                label: '最后一个',
                value: 'last',
              },
              {
                label: '直到平面',
                value: 'plane',
              },
              {
                label: '直到曲面',
                value: 'surface',
              },
            ]}
            onChange={(v) => {
              if (v === val.mode) {
                return;
              }
              onChange({
                reverse: val.reverse,
                mode: v,
                distance: v === 'distance' ? 20 : null,
              });

              previewer.clear();
            }}
          />
          {['distance', 'next', 'last'].includes(val.mode) && !isDisabled && (
            <Icon
              className='ml-2 cursor-pointer'
              name={
                val.reverse
                  ? 'commands/common/flip-direction-2'
                  : 'commands/common/flip-direction-1'
              }
              onClick={() => {
                onChange({ ...val, reverse: !val.reverse });
              }}
            />
          )}
        </div>
        {val.mode === 'distance' && (
          <InputWithFormat
            className='w-full'
            value={val.distance}
            inputType={ParameterObjectType.Length}
            disabled={isDisabled}
            disableParameter={isDisabled}
            onChange={(v) => onChange({ ...val, distance: v as number })}
          />
        )}
        {val.mode === 'plane' && (
          <PickInputForFocus
            allowPickType={[PickInputValueType.Plane]}
            onPick={(v) => {
              const surfPoint = getCurrentSurfacePoint(v[0]);
              onChange({
                ...val,
                plane: v[0],
                surfPoint: surfPoint
                  ? [surfPoint.x, surfPoint.y, surfPoint.z]
                  : null,
              });
            }}
            value={[val.plane]}
            placeholder='请输入平面'
            disabled={initingEdit}
          />
        )}
        {val.mode === 'surface' && (
          <PickInputForFocus
            allowPickType={[PickInputValueType.Surface]}
            onPick={(v) => {
              const surfPoint = getCurrentSurfacePoint(v[0]);
              onChange({
                ...val,
                surface: v[0],
                surfPoint: surfPoint
                  ? [surfPoint.x, surfPoint.y, surfPoint.z]
                  : null,
              });
            }}
            value={[val.surface]}
            placeholder='请输入曲面'
            disabled={initingEdit}
          />
        )}
      </>
    );
  };

  // 获取选中 plane/surface 时，对应的 面上的 一个点的坐标
  const getCurrentSurfacePoint = (surface: pickedFaceData) => {
    if (surface == null) {
      return null;
    }

    const rayCaster = ctx2.viewer.sceneSetup.createRaycaster(
      eventCenterManager.activeEventCenter.mouse.x,
      eventCenterManager.activeEventCenter.mouse.y,
    );

    const res = rayCaster.intersectObject(surface.assisObj, true);
    const firstPoint = res.sort((a, b) => a.distance - b.distance)[0]?.point;
    return firstPoint;
  };

  return (
    <CommandDialog
      title={title}
      onClose={onClose}
      placement='topLeft'
    >
      <CommandFormWrapper>
        <PickInputForFocus
          allowPickType={[
            PickInputValueType.Plane,
            PickInputValueType.Curve,
            PickInputValueType.Sketch,
          ]}
          allowInfinitePlane={false}
          onPick={(v) => {
            setPickedProfile(v);
          }}
          value={pickedProfile}
          placeholder='请输入轮廓/曲面'
          disabled={initingEdit}
          isMulti
          autoFocus
        />
        <PickInputForFocus
          allowPickType={[PickInputValueType.Plane, PickInputValueType.Line]}
          onPick={(v) => {
            setPickedVector(v[0]);
          }}
          value={pickedVector == null ? [] : [pickedVector]}
          placeholder={
            isProfileSketch ? '请输入方向（默认为法线）' : '请输入方向'
          }
          disabled={initingEdit}
        />
        {renderLimit(setFirstLimit, firstLimit, '第一限制', false)}
        {renderLimit(setSecondLimit, secondLimit, '第二限制', true)}

        <Checkbox
          disabled={firstLimit.mode !== 'distance'}
          checked={mirrorLimit}
          onChange={(e) => setMirrorLimit(e.target.checked)}
        >
          镜像范围
        </Checkbox>
        {cmdId === 'cmdExtrudeCut' && (
          <Checkbox
            checked={reverseSideCut}
            onChange={(e) => setReverseSideCut(e.target.checked)}
          >
            反侧切除
          </Checkbox>
        )}
      </CommandFormWrapper>
      <CommandFormFooter
        onSubmit={finishAction}
        onClose={onClose}
        isSubmitDisabled={!finishValid}
      />
    </CommandDialog>
  );
}
